package zdream.control.world.buff;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;

import zdream.control.world.Box;
import zdream.control.world.MFoe;
import zdream.control.world.MFoeBuff;
import zdream.control.world.MHookType;
import zdream.control.world.MHooks;
import zdream.utils.math.Polygon;
import zdream.utils.math.Rectangle;

import static zdream.control.world.MFoeConstants.*;

/**
 * 检查攻击模块 (碰撞伤害), 在所有 Foe 移动完成后启动检查的逻辑.
 * 
 * @author Zdream
 * @since 0.0.1
 * @date 2022-06-06
 */
public class AttackModule extends MFoeBuff {
	public static final String CATEGORY = "attack";
	public AttackModule(MFoe foe) {
		super(foe);
	}

	@Override
	public void init() {
		onHook(MHookType.TICK)
				.withPriority(0)
				.handle(this::checkBoxOverlap)
				.buildAndSave();
	}

	public void checkBoxOverlap(MHooks.MHookContext rawContext) {
		if (damage == DAMAGE_DISABLE) {
			return;
		}
		handleAttack();
	}

	@Override
	public String getCategory() {
		return CATEGORY;
	}

	/* **********
	 *   攻击   *
	 ********** */

	public static final int DAMAGE_NONE = 0;
	public static final int DAMAGE_DISABLE = -1;
	/**
	 * 设置为 0 就是无碰撞伤害 (但是有攻击动作).
	 * 设置为 -1 表示禁用攻击.
	 */
	public int damage = -1;

	/**
	 * <p>伤害等级. 防御等级大于等于伤害等级, 伤害不生效.
	 * <p>一般:
	 * <li>怪碰撞伤害等级 1
	 * <li>地形的刺等级 3
	 * <li>安全帽默认防御等级 4
	 * <li>怪物用身体进行撞击时 / 陷阱伤害等级 5
	 * <li>大多数子弹伤害等级 7
	 * <li>怪 / 精英怪 / BOSS 无敌时间防御等级 8
	 * <li>怪物重击 9
	 * <li>主角团无敌时间防御等级 10
	 * <li>安全帽躲到壳里面的防御等级 12
	 * <li>特殊: 蓄力炮 13
	 * </li></p>
	 *
	 * @see zdream.control.world.MFoeConstants#DAMAGE_LEVEL_NONE
	 */
	public byte damageLevel = 1;
	/**
	 * 用于在判断攻击时, 记录 box 扫过的范围.
	 */
	private final Polygon polygon = new Polygon();

	public final List<Predicate<MFoe>> ignoredList = new ArrayList<>();

	/**
	 * 能接收攻击的 foe 类型白名单
	 */
	public String[] targetTypes;
	/**
	 * 攻击到的 Foe, 备选列表
	 */
	private final List<MFoe> candidates = new ArrayList<>();
	/**
	 * 攻击到敌人后就停止判定攻击吗?
	 */
	public boolean stopAfterAttackOnce = false;
	/**
	 * 攻击到敌人后就删除自己吗?
	 */
	public boolean destroyAfterAttackReceived = false;
	/**
	 * 如果敌人没死, 才删除自己吗?
	 */
	public boolean destroyWhenEnemyNotDie = false;

	public void setDamage(float visualDamage) {
		setDamage(visualDamage, DAMAGE_LEVEL_PURE);
	}

	public void setDamage(float visualDamage, byte level) {
		if (visualDamage >= 0) {
			damage = (int) (visualDamage * HP_TOKEN);
			addTag(TAG_HAS_DAMAGE);
		} else {
			damage = DAMAGE_DISABLE;
			removeTag(TAG_HAS_DAMAGE);
		}
		this.setDamageLevel(level);
	}

	public void clearDamage() {
		setDamage(DAMAGE_DISABLE, DAMAGE_LEVEL_NONE);
	}

	public void setDamageLevel(byte level) {
		this.damageLevel = level;
	}

	private void handleAttack() {
		if (damage == DAMAGE_DISABLE || foe.isDestroyed()) {
			return;
		}

		candidates.clear();
		findEnemies();
		if (candidates.isEmpty()) {
			return;
		}

		boolean selfDestroy = false;
		sortEnemiesAttacked();
		for (MFoe target : candidates) {
			if (target.isDestroyed()) {
				continue;
			}

			final MHooks.DamageContext context = target.triggerHook(MHookType.RECEIVE_DAMAGE).as();
			context.append(foe, MHookType.APPLY_DAMAGE);
			context.withAttacker(foe)
					.withDefender(target)
					.withDamage(damage, damageLevel)
					.run();

			boolean accepted = context.accepted || context.absorbed;
			selfDestroy |= (accepted && destroyAfterAttackReceived);
			selfDestroy |= (accepted && destroyWhenEnemyNotDie && !context.targetDie);

			boolean scanningNext = !context.absorbed && !context.blocked;
			if (accepted && stopAfterAttackOnce) {
				scanningNext = false;
			}
			if (!scanningNext) {
				break;
			}
		}

		if (selfDestroy) {
			foe.destroy();
		}
	}

	private void sortEnemiesAttacked() {
		final float vx = foe.velocityX;
		final float vy = foe.velocityY;
		if (vx == 0 && vy == 0) {
			return;
		}

		if (Math.abs(vx) > Math.abs(vy)) {
			if (vx > 0) {
				candidates.sort(Comparator.comparingDouble(foe -> foe.pos.left()));
			} else {
				candidates.sort(Comparator.comparingDouble(foe -> -foe.pos.right()));
			}
		} else {
			if (vy > 0) {
				candidates.sort(Comparator.comparingDouble(foe -> foe.pos.bottom()));
			} else {
				candidates.sort(Comparator.comparingDouble(foe -> -foe.pos.top()));
			}
		}
	}

	private void filterEnemies() {
		candidates.removeIf(foe -> {
			if (foe == this.foe || foe.side == this.foe.side) {
				return true;
			}

			if (targetTypes != null) {
				boolean matched = Arrays.stream(targetTypes).anyMatch(type -> type.equals(foe.type));
				if (!matched) {
					return true;
				}
			}

			return ignoredList.stream().anyMatch(p -> p.test(foe));
		});
	}

	private void findEnemies() {
		Box box = foe.flush();
		if (box.lastAnchorX == box.anchorX && box.lastAnchorY == box.anchorY) {
			foe.world.findOverlapFoeAndPutInList(candidates, box.pos);
			filterEnemies();
			return;
		}

		float deltaX = box.anchorX - box.lastAnchorX;
		float deltaY = box.anchorY - box.lastAnchorY;
		if (deltaX == 0) {
			foe.world.findOverlapFoeAndPutInList(candidates,
					box.pos.x,
					deltaY > 0 ? box.pos.y - deltaY : box.pos.y + box.pos.height,
					box.pos.width,
					Math.abs(deltaY) + box.pos.height);
			filterEnemies();
			return;
		}
		if (deltaY == 0) {
			foe.world.findOverlapFoeAndPutInList(candidates,
					deltaX > 0 ? box.pos.x - deltaX : box.pos.x + box.pos.width,
					box.pos.y,
					Math.abs(deltaX) + box.pos.width,
					box.pos.height);
			filterEnemies();
			return;
		}

		// deltaX != 0 && deltaY != 0
		float dxLeft = box.pos.x;
		float dxRight = dxLeft + box.pos.width;
		float dyBottom = box.pos.y;
		float dyTop = dyBottom + box.pos.height;
		float sxLeft = dxLeft - deltaX;
		float sxRight = dxRight - deltaX;
		float syBottom = dyBottom - deltaY;
		float syTop = dyTop - deltaY;
		if (deltaX > 0) {
			if (deltaY > 0) {
				polygon.setVertices(new float[] {
					sxLeft, syBottom,
					sxLeft, syTop,
					dxLeft, dyTop,
					dxRight, dyTop,
					dxRight, dyBottom,
					sxRight, syBottom});
			} else { // deltaY < 0
				polygon.setVertices(new float[] {
					sxLeft, syBottom,
					sxLeft, syTop,
					sxRight, syTop,
					dxRight, dyTop,
					dxRight, dyBottom,
					dxLeft, dyBottom});
			}
		} else { // deltaX < 0
			if (deltaY > 0) {
				polygon.setVertices(new float[] {
					sxLeft, syBottom,
					dxLeft, dyBottom,
					dxLeft, dyTop,
					dxRight, dyTop,
					sxRight, syTop,
					sxRight, syBottom});
			} else { // deltaY < 0
				polygon.setVertices(new float[] {
					sxLeft, syTop,
					sxRight, syTop,
					sxRight, syBottom,
					dxRight, dyBottom,
					dxLeft, dyBottom,
					dxLeft, dyTop});
			}
		}
		Rectangle bounding = polygon.getBoundingRectangle();
		foe.world.findOverlapFoeAndPutInList(candidates,
				bounding.x,
				bounding.y,
				bounding.width,
				bounding.height);

		filterEnemies();
		if (candidates.size() == 0) {
			return;
		}
		// 判断方形 box 和 polygon 是否重合
		candidates.removeIf(foe -> !overlapBoxAndPolygon(foe.pos));
	}

	private boolean overlapBoxAndPolygon(Rectangle rect) {
		float[] vertices = polygon.getVertices();
		final int numFloats = vertices.length;

		// 如果存在点在方形区域内, 那就是了
		for (int i = 0; i < numFloats; i += 2) {
			if (rect.contains(vertices[i], vertices[i + 1])) {
				return true;
			}
		}

		// 如果方形有任何一个点在多边形内, 也是
		float rx1 = rect.x, rx2 = rx1 + rect.width, ry1 = rect.y, ry2 = ry1 + rect.height;
		if (polygon.contains(rx1, ry1) || polygon.contains(rx1, ry2) || polygon.contains(rx2, ry1) || polygon.contains(rx2, ry2)) {
			return true;
		}

		// 判断线段和区域是否相交
		for (int i = 0; i < numFloats; i += 2) {
			float x1 = vertices[i];
			float x2 = vertices[(i + 2) % numFloats];
			if (x1 > rx2 ^ x2 > rx2 || x1 < rx1 && x2 < rx1) {
				continue;
			}
			float y1 = vertices[i + 1];
			float y2 = vertices[(i + 3) % numFloats];
			if (y1 > ry2 && y2 > ry2 || y1 < ry1 && y2 < ry1) {
				continue;
			}
			if (yfVector2SegmentIntersects(x1, y1, x2, y2, rx1, ry1, rx1, ry2)
					|| yfVector2SegmentIntersects(x1, y1, x2, y2, rx1, ry2, rx2, ry2)
					|| yfVector2SegmentIntersects(x1, y1, x2, y2, rx2, ry2, rx2, ry1)
					|| yfVector2SegmentIntersects(x1, y1, x2, y2, rx2, ry1, rx1, ry1)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 检查点 (x, y) 是否在线段 (x1, y1) - (x2, y2) 的左边 (几何学的左右)
	 */
	private static float yfVector2IsLeft(float x, float y, float x1, float y1, float x2, float y2) {
		return (x1-x)*(y2-y) - (x2-x)*(y1-y);
	}

	/**
	 * 线段 (x11, y11) - (x12, y12) 和 (x21, y21) - (x22, y22) 是否相交
	 */
	private static boolean yfVector2SegmentIntersects(
			float x11, float y11, float x12, float y12,
			float x21, float y21, float x22, float y22) {
		float leftS, leftE;
		leftS = yfVector2IsLeft(x11, y11, x21, y21, x22, y22);
		leftE = yfVector2IsLeft(x12, y12, x21, y21, x22, y22);
		if (leftS * leftE > 0) {
			return false; // (x11, y11), (x12, y12) 在另一条直线的同侧
		}

		leftS = yfVector2IsLeft(x21, y21, x11, y11, x12, y12);
		leftE = yfVector2IsLeft(x22, y22, x11, y11, x12, y12);
		if (leftS * leftE > 0) {
			return false; // (x21, y21), (x22, y22) 在另一条直线的同侧
		}

		return true;
	}

	/* **********
	 *   工厂   *
	 ********** */

	public static AttackModuleFactory builder(MFoe foe) {
		return new AttackModuleFactory(foe);
	}

	public static class AttackModuleFactory {
		AttackModule module;
		public AttackModuleFactory(MFoe foe) {
			module = new AttackModule(foe);
		}
		public AttackModuleFactory damage(float damage) {
			module.setDamage(damage);
			return this;
		}
		public AttackModuleFactory damageLevel(byte damageLevel) {
			module.damageLevel = damageLevel;
			return this;
		}
		public AttackModuleFactory ignoreFoe(MFoe... foes) {
			module.ignoredList.add(foe -> Arrays.stream(foes).anyMatch(others -> foe == others));
			return this;
		}
		public AttackModuleFactory ignoreSide(byte side) {
			module.ignoredList.add(foe -> foe.side == side);
			return this;
		}
		public AttackModuleFactory targetTypesIsEntity() {
			module.targetTypes = new String[] {MFoe.TYPE_FOE, MFoe.TYPE_LEADER, MFoe.TYPE_ELITE};
			return this;
		}
		public AttackModuleFactory stopAfterAttackOnce() {
			module.stopAfterAttackOnce = true;
			return this;
		}
		public AttackModuleFactory destroyAfterAttackReceived() {
			module.destroyAfterAttackReceived = true;
			return this;
		}
		public AttackModuleFactory destroyWhenEnemyNotDie() {
			module.destroyWhenEnemyNotDie = true;
			return this;
		}
		public AttackModule create() {
			module.foe.addBuff(module);
			return module;
		}
	}
}
